#!/usr/bin/env bash
# Copyright (c) 2021-2024 maminjie <canpool@163.com>
# SPDX-License-Identifier: MulanPSL-2.0

method_def obs

usage_obs() {
    module_usage "obs" "Open Build Service command-line tool, base osc"
}

do_obs() {
    module_do "obs" "$@"
}

obs_usage_help() {
    module_usage_help obs
}

# obs_do_help subcmd
obs_do_help() {
    module_do_help obs "$1"
}

# __obs_get_subcmd cmd
#   Get the fullname of cmd
# Returns:
#   "" or fullname
__obs_get_subcmd() {
    local cmd=""
    case "${1}" in
        "h"|"help")
            cmd="help"
            ;;
        "ls"|"list")
            cmd="list"
            ;;
        "br"|"branch")
            cmd="branch"
            ;;
        "co"|"checkout")
            cmd="checkout"
            ;;
        "c"|"create")
            cmd="create"
            ;;
        "isp"|"importsrcpkg")
            cmd="importsrcpkg"
            ;;
        "up"|"update")
            cmd="update"
            ;;
        "rb"|"rebuild")
            cmd="rebuild"
            ;;
        "sflag"|"setflag")
            cmd="setflag"
            ;;
        "binfo"|"buildinfo")
            cmd="buildinfo"
            ;;
        "gbin"|"getbin")
            cmd="getbin"
            ;;
        "gbins"|"getbinaries")
            cmd="getbinaries"
            ;;
        "gbinl"|"getbinlist")
            cmd="getbinlist"
            ;;
        "gfile"|"getfile")
            cmd="getfile"
            ;;
        "gpkg"|"getpackage")
            cmd="getpackage"
            ;;
        "unres"|"unresolved")
            cmd="unresolved"
            ;;
        "pkgr"|"pkgresults")
            cmd="pkgresults"
            ;;
        "gblogs"|"getbuildlogs")
            cmd="getbuildlogs"
            ;;
        "gjobt"|"getjobtime")
            cmd="getjobtime"
            ;;
        "rdel"|"rdelete")
            cmd="rdelete"
            ;;
        "conf"|"config")
            cmd="config"
            ;;
        *)
            ;;
    esac
    echo "$cmd"
}


obs_usage_branch() {
printf "branch (br): Branch some packages from obs

usage:
    ${PROG} obs br SOURCEPROJECT TARGETPROJECT PACKAGES
\n"
}

# obs_do_branch srcprj dstprj pkgs
obs_do_branch() {
    if [ $# -ne 3 ]; then
        obs_usage_branch; exit
    fi
    local pkgs=$(get_list "$3")

    local k=1
    for pkg in $pkgs; do
        echo "$k) branching $1/$pkg"
        osc branch "$1" "$pkg" "$2" 2> /dev/null
        [[ $? -ne 0 ]] && echo "failed to branch"
        ((k++))
    done
}


# obs_check_project [prj]
#   Check if the directory is an obs project
obs_check_project() {
    local prj=$1
    if [ -z "$prj" ]; then
        prj=$(pwd)
    elif [ ! -d "$prj" ]; then
        return 1
    fi
    ls -a "$prj"/.osc/_project &>/dev/null
    if [ $? -ne 0 ]; then
        return 1
    fi
    return 0
}

obs_usage_checkout() {
printf "checkout (co): Check out content from the repository of obs

usage:
    ${PROG} obs co SOURCEPROJECT PACKAGES
    ${PROG} obs co PACKAGES                  # current dir must be obs project
    ${PROG} obs co SOURCEPROJECT/PACKAGE
\n"
}

# obs_do_checkout srcprj pkgs
# obs_do_checkout pkgs
# obs_do_checkout srcprj/pkg
obs_do_checkout() {
    local prj=""
    local pkgs=""
    if [ $# -eq 2 ]; then
        prj=$1
        pkgs=$(get_list "$2")
    elif [ $# -eq 1 ]; then
        # prj/pkg
        eval $(echo "$1" | awk -F "/" '{
            if (NF >= 2) {
                printf("prj=%s;pkgs=%s;", $(NF-1), $NF)
            }
        }')
        if [ -z "$prj" ]; then
            obs_check_project
            if [ $? -ne 0 ]; then
                obs_usage_checkout; exit
            fi
            pkgs=$(get_list "$1")
        fi
    else
        obs_usage_checkout; exit
    fi

    local k=1
    for pkg in $pkgs; do
        local pkg_path=$prj/$pkg
        if [ -z "$prj" ]; then
            pkg_path=$pkg
        fi
        echo "$k) checking out $pkg_path"
        osc co "$pkg_path" 2> /dev/null
        pushd "$pkg_path" || return 1
        osc_update
        popd || return 1
        ((k++))
    done
}


obs_usage_create() {
printf "create (c): Create the package if it doesn't exist

usage:
    ${PROG} obs c PACKAGES PROJECT
    ${PROG} obs c PACKAGES                  # current dir must be obs project
\n"
}

obs_do_create() {
    if [ $# -lt 1 ]; then
        obs_usage_create; exit
    fi
    local pkgs=$(get_list "$1")
    local prj="$2"
    if [ -z "$prj" ]; then
        local cur_dir=$(pwd)
        if [ ! -f "$cur_dir/.osc/_project" ]; then
            echo "current dir is not obs project."; exit
        fi
        prj=${cur_dir##*/}
    fi
    for pkg in $pkgs; do
        local meta="<package name=\"${pkg}\" project=\"${prj}\"><title/><description/></package>"
        osc api -X PUT /source/$prj/$pkg/_meta --data "$meta"
    done
}


obs_usage_importsrcpkg() {
printf "importsrcpkg (isp): Import some new packages from src.rpm

usage:
     ${PROG} obs importsrcpkg PROJECT PACKAGES ...

params:
    PROJECT - obs project
    ... - yumdownloader global options, like: --repo reponame

notes:
    The obs PROJECT directory must exist in the current directory
\n"
}

# obs_do_importsrcpkg project pkgs ...
obs_do_importsrcpkg() {
    if [ $# -lt 2 ]; then
        obs_usage_importsrcpkg; exit
    fi
    local prj=$1
    local pkgs=$(get_list "$2")
    shift 2

    obs_check_project "$(pwd)/$prj"
    if [ $? -ne 0 ]; then
        echo "No obs project \"$prj\" in current directory, checkout it firstly"; exit
    fi

    for pkg in $pkgs; do
        yumdownloader --source "$pkg" $@
        if [ $? -ne 0 ]; then
            continue
        else
            local prj_tmp=$(osc search --package "$pkg" | grep "${prj##*/}" | awk '{print $1}')
            if [ "$prj" = "$prj_tmp" ]; then
                echo "$pkg exist"
                continue;
            fi
            osc importsrcpkg ${pkg}*.src.rpm -p "$prj" -n "$pkg"
            cd "$prj/$pkg" || return 1
            osc ar
            osc ci -m "init"
            cd - || return 1
        fi
    done
}


obs_usage_update() {
printf "update (up): Update a working copy from obs

usage:
    ${PROG} obs up
\n"
}

# obs_do_update
obs_do_update() {
    osc_update
}


obs_usage_rebuild() {
printf "rebuild (rb): Trigger package rebuilds

usage:
    ${PROG} obs rb PROJECT PACKAGES
\n"
}

# obs_do_rebuild ROJECT PACKAGES
obs_do_rebuild() {
    if [ $# -lt 2 ]; then
        obs_usage_rebuild; exit
    fi
    local prj=$1
    local pkgs=$(get_list "$2")
    for pkg in $pkgs; do
        osc rebuild $prj $pkg
    done
}


obs_usage_setflag() {
printf "setflag (sflag): Modify or set a defined flag for package

usage:
    ${PROG} obs setflag PROJECT REPO ARCH FLAG STATUS PACKAGES

params:
      REPO   - setflag for given repository (all/..)
      ARCH   - setflag for given arch (all/..)
      FLAG   - modify this flag (build/publish/..) for setflag command
      STATUS - enable or disable for setflag command

example:
    ${PROG} obs setflag PROJECT all all build disable PACKAGE
    ${PROG} obs setflag PROJECT standard_aarch64 all build disable PACKAGE
\n"
}

# obs_do_setflag prj repo arch flag status pkgs
#   refer to https://build.opensuse.org/apidocs/index
obs_do_setflag() {
    if [ $# -ne 6 ]; then
        obs_usage_setflag; exit
    fi
    local prj=$1
    local repo=$2
    local arch=$3
    local flag=$4
    local status=$5
    local pkgs=$(get_list "$6")
    local content=""

    for pkg in $pkgs; do
        if [ "$repo" = "all" ]; then
            if [ "$arch" = "all" ]; then
                content="/source/${prj}/${pkg}?cmd=set_flag&flag=${flag}&status=${status}"
            else
                content="/source/${prj}/${pkg}?cmd=set_flag&arch=${arch}&flag=${flag}&status=${status}"
            fi
        else
            if [ "$arch" = "all" ]; then
                content="/source/${prj}/${pkg}?cmd=set_flag&repository=${repo}&flag=${flag}&status=${status}"
            else
                content="/source/${prj}/${pkg}?cmd=set_flag&repository=${repo}&arch=${arch}&flag=${flag}&status=${status}"
            fi
        fi
        echo "set $status for $prj/$pkg ($repo/$arch) $flag flag"
        osc api -X POST "$content" 2> /dev/null
    done
}


obs_usage_buildinfo() {
printf "buildinfo (binfo): Display the package build infos

usage:
    ${PROG} obs binfo PROJECT/REPO/ARCH/PACKAGE
    ${PROG} obs binfo PROJECT REPO ARCH PACKAGE
\n"
}

# obs_get_bdep_tagval info tag
obs_get_bdep_tagval() {
    echo "$1" | sed -r "s/.*$2=\"([^\"]+)\".*$/\1/g" | grep -v "<bdep"
}

# obs_do_buildinfo prj/repo/arch/pkg
# obs_do_buildinfo prj repo arch pkg
obs_do_buildinfo() {
    # content=/build/<project>/<repository>/<arch>/<package>/_buildinfo
    local content=""
    if [ $# -eq 1 ]; then
        content="/build/$1/_buildinfo"
    elif [ $# -eq 4 ]; then
        content="/build/$1/$2/$3/$4/_buildinfo"
    else
        obs_usage_buildinfo; exit
    fi
    local infos=$(osc api -X GET "$content" 2> /dev/null)
    local bdeps=$(echo "$infos" | grep "<bdep" | grep -v "preinstall")
    local pkgs=()
    OLD_IFS="$IFS"; IFS=$'\n'
    for info in $bdeps; do
        local name=$(obs_get_bdep_tagval "$info" name)
        local epoch=$(obs_get_bdep_tagval "$info" epoch)
        local version=$(obs_get_bdep_tagval "$info" version)
        local release=$(obs_get_bdep_tagval "$info" release)
        local pkg="$name-$version-$release"
        if [ -n "$epoch" ]; then
            pkg="$name-$epoch:$version-$release"
        fi
        array_add pkgs "$pkg"
    done
    IFS="$OLD_IFS"
    array_uniq pkgs
}


obs_usage_getbin() {
printf "getbin (gbin): Get binary from the obs

usage:
    ${PROG} obs getbin URL

example:
    ${PROG} obs getbin https://xxx/project/package/repo/arch/binaryname
    ${PROG} obs getbin project/package/repo/arch/binaryname

notes:
    URL - you can copy the url from the obs directly
\n"
}

# obs_do_getbin url
obs_do_getbin() {
    if [ $# -ne 1 ]; then
        obs_usage_getbin; exit
    fi
    # project/package/repo/arch/binaryname
    local prj=""
    eval $(echo "$1" | awk -F "/" '{
        if (NF >= 5) {
            printf("prj=%s;pkg=%s;repo=%s;arch=%s;bname=%s;", $(NF-4), $(NF-3), $(NF-2), $(NF-1), $NF)
        }
    }')
    if [ -z "$prj" ]; then
        echo "url format error"
        exit
    fi
    # /build/project/repo/arch/package/binaryname
    local content="/build/$prj/$repo/$arch/$pkg/$bname"

    echo "downloading $bname ..."
    osc api -X GET "$content" > "$bname" 2> /dev/null
}


obs_usage_getbinaries() {
printf "getbinaries (gbins): Get binaries from the obs

usage:
    ${PROG} obs gbins PROJECT REPO ARCH PACKAGES
\n"
}

# obs_do_getbinaries prj repo arch pkgs
obs_do_getbinaries() {
    if [ $# -ne 4 ]; then
        obs_usage_getbinaries; exit
    fi
    local pkgs=$(get_list "$4")

    for pkg in $pkgs; do
        osc getbinaries "$1" "$pkg" "$2" "$3" 2> /dev/null
    done
}


obs_usage_getbinlist() {
printf "getbinlist (gbinl): Get binary list from obs (deprecated)

usage:
    ${PROG} obs gbinl PROJECT PACKAGE
    ${PROG} obs gbinl PROJECT PACKAGE REPO ARCH

notes:
    \"osc ls\" can be used instead of gbinl.
    # osc ls -b PROJECT PACKAGE
    # osc ls -b PROJECT PACKAGE REPO ARCH
\n"
}

# obs_do_getbinlist prj pkg
# obs_do_getbinlist prj pkg repo arch
obs_do_getbinlist() {
    if [ $# -eq 2 ]; then
        local prj=$1
        local pkg=$2
        local results=$(osc results $prj $pkg 2> /dev/null)
        local repo_arch=$(echo "$results" | awk '{print $1"/"$2}')
        local k=1
        for i in $(echo "$repo_arch"); do
            # /build/project/repo/arch/package
            local content="/build/$prj/$i/$pkg"
            color_print "$CLR_GREEN" "$k) $i:"
            osc api -X GET "$content" 2> /dev/null
            ((k++))
        done
    elif [ $# -eq 4 ]; then
        # local content="/build/$1/$3/$4/$2"
        # osc api -X GET "$content" 2> /dev/null | grep "\.rpm" | awk '{print $2}' | sed -e 's/"//g' -e 's/filename=//g'
        osc ls -b "$1" "$2" "$3" "$4" 2> /dev/null | grep "\.rpm"
    else
        obs_usage_getbinlist; exit
    fi
}


obs_usage_getfile() {
printf "getfile (gfile): Get file from the obs

usage:
    ${PROG} obs gfile URL

example:
    ${PROG} obs gfile https://xxx/project/package/filename?xxxx
    ${PROG} obs gfile project/package/filename

notes:
    URL - you can copy the url from the obs directly
\n"
}

# obs_do_getfile url
obs_do_getfile() {
    if [ $# -ne 1 ]; then
        obs_usage_getfile; exit
    fi
    # xxx/project/package/filename?xxxx
    local url=$(echo "$1" | sed 's/\?.*//')
    # xxx/project/package/filename
    local prj=""
    eval $(echo "$url" | awk -F "/" '{
        if (NF >= 3) {
            printf("prj=%s;pkg=%s;fname=%s;", $(NF-2), $(NF-1), $NF)
        }
    }')
    if [ -z "$prj" ]; then
        echo "url format error"; exit
    fi
    # project/package/filename
    local content="$prj/$pkg/$fname"
    local newfile=${fname##*:}

    echo "downloading $newfile ..."
    osc co "$content" 2> /dev/null
    if [ $? -eq 0 ] && [ "$fname" != "$newfile" ]; then
        mv "$fname" "$newfile"
    fi
}


obs_usage_getpackage() {
printf "getpackage (gpkg): Get rpm package from the obs

usage:
    ${PROG} obs gpkg PROJECT PACKAGES [OTHER_ARCHS]

params:
    OTHER_ARCHS - specify other ARCH list, such as \"loongarch64 sw_64\",
                  default support x86_64 and aarch64

example:
    # get from x86_64 and aarch64 ARCHs
    ${PROG} obs gpkg project pkg

    # get from x86_64,aarch64 and loongarch64 ARCHs
    ${PROG} obs gpkg project pkg loongarch64

    ${PROG} obs gpkg project \"pkg1 pkg2\" \"arch1 arch2\"
\n"
}

# obs_do_getpackage prj pkgs [other_archs]
obs_do_getpackage() {
    if [ $# -ne 2 ]; then
        obs_usage_getpackage; exit
    fi
    local prj="$1"
    local pkgs=$(get_list "$2")
    local archs=$(get_list "$3")
    archs="x86_64 aarch64 $archs"

    local outdir="update_$(date +"%Y%m%d")"
    [[ -d "$outdir" ]] && rm -rf "$outdir"
    mkdir -p "$outdir"

    local src_outdir="$outdir/source/Packages"
    mkdir -p "$src_outdir"

    for arch in $archs; do
        local repo="standard_$arch"
        local arch_outdir="$outdir/$arch/Packages"
        mkdir -p "$arch_outdir"
        local bin_outdir="$outdir/binaries"
        for pkg in $pkgs; do
            echo "getting $prj/$repo/$arch/$pkg ..."
            osc getbinaries "$prj" "$pkg" "$repo" "$arch" --sources --debug -q -d "$bin_outdir" &>/dev/null
            if [ $? -ne 0 ]; then
                continue
            fi
            cp $bin_outdir/*.src.rpm "$src_outdir"
            rm -f $bin_outdir/*.src.rpm
            cp $bin_outdir/*.rpm "$arch_outdir"
            rm -rf "$bin_outdir"
        done
    done
}


obs_usage_list() {
printf "list (ls): List the packages of project from obs

usage:
    ${PROG} obs ls PROJECT REPO ARCH STATE

parameter:
    STATE - package state, value as follows:
        s scheduled
        S signing
          disabled
        B broken
        f finished
        b blocked
        % building
        L locked
        . succeeded
        U unresolvable
        d dispatching
        F failed
        x excluded
\n"
}

# obs_do_list prj repo arch state
obs_do_list() {
    if [ $# -ne 4 ]; then
        obs_usage_list; exit
    fi
    local prj=$1
    local repo=$2
    local arch=$3
    local state=$4
    local pkgs=$(osc pr $prj -s $state -r $repo -a $arch -q -V 2> /dev/null | sed 1d | sed '$d' | sed '$d' | awk '{print $2}')

    if [ -n "$pkgs" ]; then
        echo "$pkgs"
    fi
}

obs_usage_unresolved() {
printf "unresolved (unres): Analyse the unresolved packages from obs

usage:
    ${PROG} obs unres PROJECT REPO ARCH all ...
    ${PROG} obs unres PROJECT REPO ARCH PACKAGES ...

params:
    ... - dnf global options, like: --repo reponame
\n"
}

# obs_do_unresolved prj repo arch pkgs ...
obs_do_unresolved() {
    if [ $# -lt 4 ]; then
        obs_usage_unresolved; exit
    fi
    local prj=$1
    local repo=$2
    local arch=$3
    local unres_pkgs=""
    if [ "$4" = "all" ]; then
        unres_pkgs=$(osc pr $prj -s U -r $repo -a $arch -q -V 2> /dev/null | sed 1d | sed '$d' | sed '$d' | awk '{print $2}')
    else
        unres_pkgs=$(get_list "$4")
    fi
    shift 4
    dnf_set_global_option "$@"
    # do query
    {
    printf "%s|%s|%s|%s\n" "# PKG" "# DEP" "# BIN" "# SRC"
    for x in $unres_pkgs; do
        local relys=$(osc r $prj $x -r $repo -a $arch -v 2> /dev/null | grep -v unresolv | awk '{print $3}' | sed -r 's/^\(//g')
        for y in $relys; do
            local bin=$(dnf_get_whatprovides "$y")
            local src="Not-Found"
            if [ -z "$bin" ]; then
                bin="Not-Found"
            else
                src=$(dnf_get_source "$bin")
                if [ -z "$src" ]; then
                    src=$bin
                fi
            fi
            printf "%s|%s|%s|%s\n" $x $y $bin $src
        done
    done
    } | format_column '|'
}

obs_usage_pkgresults() {
printf "pkgresults (pkgr): Shows package-wide build results

usage:
    ${PROG} obs pkgr PACKAGE
\n"
}

# obs_do_pkgresults pkg
obs_do_pkgresults() {
    if [ $# -ne 1 ]; then
        obs_usage_pkgresults; exit
    fi
    local package=$1
    local projects=$(osc search --package "$package" 2> /dev/null | sed '0,/^# Project/d' | awk '{print $1}')

    local i=1
    for prj in $projects; do
        local results=$(osc results "$prj" "$package" 2> /dev/null)
        color_print ${COLOR_YELLOW} "$i) $prj"
        echo "${results}" | awk -F '\n' '{
            for (k = 1; k <= NF; k++) {
                printf("\t%s\n", $k)
            }
        }'
        ((i++))
    done
}


obs_usage_getbuildlogs() {
printf "getbuildlogs (gblogs): Get build logs from the obs

usage:
    ${PROG} obs gblogs PROJECT REPO ARCH PACKAGES
\n"
}

# obs_do_getbuildlogs prj repo arch pkgs
obs_do_getbuildlogs() {
    if [ $# -ne 4 ]; then
        obs_usage_getbuildlogs; exit
    fi
    local pkgs=$(get_list "$4")

    for pkg in $pkgs; do
        local content="/build/$1/$2/$3/$pkg/_log"
        osc api -X GET "$content" 2> /dev/null 1> "${pkg}"_log
    done
}


obs_usage_getjobtime() {
printf "getjobtime (gjobt): Get the job average time from the obs

usage:
    ${PROG} obs gjobt PROJECT REPO ARCH PACKAGES
\n"
}

__obs_do_getjobtime() {
    printf "%s,%s,%s,%s,%s,%s\n" "Package" "Polynomial" "Count" "Total(s)" "Average(s)" "Average(m)"
    for pkg in $pkgs; do
        local content="/build/$1/$2/$3/_jobhistory?package=$pkg&code=succeeded&limit=10"
        local infos=$(osc api -X GET "$content" 2> /dev/null)
        local values=$(echo "$infos" | grep "starttime" | awk '{print $8i","$9}' | sed -r 's/"|starttime|endtime|=//g')
        local cnt=$(echo "$values" | wc -l)
        local total=0
        local poly=""
        for val in $values; do
            local starttime=$(echo "$val" | awk -F ','  '{print $1}')
            local endtime=$(echo "$val" | awk -F ',' '{print $2}')
            local difftime=$((endtime-starttime))
            if [ -z "$poly" ]; then
                poly="$difftime"
            else
                poly="$poly+$difftime"
            fi
            total=$((total+endtime-starttime))
        done
        if [ -z "$poly" ]; then
            poly="none"
        fi
        local divisor=$((cnt))
        local average="$((total/divisor)).$((total%divisor))"
        divisor=$((cnt*60))
        local average_m="$((total/divisor)).$((total%divisor))"
        printf "%s,%s,%s,%s,%s,%s\n" "$pkg" $poly "$cnt" $total $average $average_m
    done
}

# obs_do_getjobtime prj repo arch pkgs
obs_do_getjobtime() {
    if [ $# -ne 4 ]; then
        obs_usage_getjobtime; exit
    fi
    local pkgs=$(get_list "$4")

    __obs_do_getjobtime "$1" "$2" "$3" "$pkgs" | format_column ','
}


obs_usage_rdelete() {
printf "rdelete (rdel): Delete a project or packages on the obs server.

usage:
    ${PROG} obs rdelete PROJECT PACKAGES
    ${PROG} obs rdelete PROJECTS
\n"
}

# obs_do_rdelete project [packages]
obs_do_rdelete() {
    local prj=""
    local pkgs=""

    if [ $# -eq 1 ]; then
        prj=$(get_list "$1")
    elif [ $# -eq 2 ]; then
        prj=$1
        pkgs=$(get_list "$2")
    else
        obs_usage_rdelete; exit
    fi

    if [ -z "$pkgs" ]; then
        for p in $prj; do
            echo "delete project $p"
            osc rdelete "$p" -r -m "delete $p" 2> /dev/null
        done
    elif [ -n "$prj" ]; then
        for pkg in $pkgs; do
            echo "delete package $prj/$pkg"
            osc rdelete "$prj" "$pkg" -m "delete $prj/$pkg" 2> /dev/null
        done
    fi
}

obs_usage_config() {
printf "config (conf): Edit osc config file

usage:
    ${PROG} obs conf
\n"
}

obs_do_config() {
    osc_config
}
